////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Microsoft Corporation.  All rights reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Gadgeteer
{
    using Microsoft.SPOT;
    using System.Collections;
    using System;

    /// <summary>
    /// Provides a timer to enable periodic status checks or actions, or a one-off stopwatch.
    /// </summary>
    /// <remarks>
    /// This class wraps the <see cref="Microsoft.SPOT.DispatcherTimer"/> class to provide 
    /// a simpler API for your Gadgeteer application.
    /// </remarks>
    public class Timer
    {
        private DispatcherTimer dt;
        private static Hashtable activeTimers = new Hashtable();

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="interval">The interval for the timer.</param>
        public Timer(TimeSpan interval)
            : this(interval, BehaviorType.RunContinuously) { }

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="intervalMilliseconds">The interval for the timer.</param>
        public Timer(int intervalMilliseconds)
            : this(new TimeSpan(0, 0, 0, 0, intervalMilliseconds), BehaviorType.RunContinuously) { }

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="interval">The interval for the timer.</param>
        /// <param name="behavior">The behavior for the timer (run once or continuously).</param>
        public Timer(TimeSpan interval, BehaviorType behavior)
        {
            if (Program.Dispatcher == null)
            {
                Debug.Print("WARN: null Program.Dispatcher in GT.Timer constructor");
            }

            this.dt = new DispatcherTimer(Program.Dispatcher ?? Dispatcher.CurrentDispatcher);
            this.dt.Interval = interval;
            this.dt.Tick += new EventHandler(dt_Tick);
            this.Behavior = behavior;
        }

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="intervalMilliseconds">The interval for the timer.</param>
        /// <param name="behavior">The behavior for the timer (run once or continuously).</param>
        public Timer(int intervalMilliseconds, BehaviorType behavior)
            : this(new TimeSpan(0, 0, 0, 0, intervalMilliseconds), behavior) { }


        /// <summary>
        ///  The behavior of this timer - whether to run once or run continuously.
        /// </summary>
        public BehaviorType Behavior { get; set; }

        /// <summary>
        /// An enumeration of timer behaviours
        /// </summary>
        public enum BehaviorType
        {
            /// <summary>
            /// Run once when started, and then stop, so a single Tick event is generated.
            /// </summary>
            RunOnce,
            /// <summary>
            /// Run continually after being started, so Tick events are generated periodically.
            /// </summary>
            RunContinuously
        }

        void dt_Tick(object sender, EventArgs e)
        {
            try
            {
                if (Behavior == BehaviorType.RunOnce) Stop();
                if(Tick != null) Tick(this);
            }
            catch 
            {
                Debug.Print("Exception performing Timer operation");
            }
        }

        /// <summary>
        /// Starts the dispatch timer.
        /// </summary>
        /// <remarks>
        /// When you create a <see cref="Timer"/> object, by default it is not started
        /// (that is, <see cref="IsRunning"/> is <b>false</b>). When you want to use the timer,
        /// you must first call this method.
        /// </remarks>
        public void Start()
        {
            if (!activeTimers.Contains(this)) 
            {
                activeTimers.Add(this, null); 
                //Debug.Print("Added reference to active timer with interval " + Interval);
            }
            this.dt.Start();
        }

        /// <summary>
        /// Stops the dispatch timer.
        /// </summary>
        public void Stop()
        {
            if (activeTimers.Contains(this))
            {
                activeTimers.Remove(this);
                //Debug.Print("Removed reference to inactive timer with interval " + Interval);
            }
            this.dt.Stop();
        }

        /// <summary>
        /// Restarts the timer
        /// </summary>
        public void Restart()
        {
            this.dt.Stop();
            Start();
        }

        /// <summary>
        /// Gets a value that indicates whether this timer is running.
        /// </summary>
        public bool IsRunning { get { return this.dt.IsEnabled; } }

        /// <summary>
        /// Gets the interval that was assigned to this timer.
        /// </summary>
        public TimeSpan Interval { get { return this.dt.Interval; } set { this.dt.Interval = value; } }

        /// <summary>
        /// Represents the delegate that is used for the <see cref="Tick"/> event.
        /// </summary>
        /// <param name="timer">The timer associated with the tick.</param>
        public delegate void TickEventHandler(Timer timer);

        /// <summary>
        /// Raised after each timer interval specified by <see cref="Interval"/>.
        /// </summary>
        /// <remarks>
        /// This event is raised when the timer is running, that is <see cref="IsRunning"/> is <b>true</b>.
        /// Handle this event to perform periodic actions.
        /// </remarks>
        public event TickEventHandler Tick;

        /// <summary>
        /// Gets a hash code for this object
        /// </summary>
        /// <returns>The hash code</returns>
        public override int GetHashCode()
        {
            return this.dt.GetHashCode();
        }

        /// <summary>
        /// Gets the hardware uptime, i.e. the time interval since the hardware was powered on.
        /// </summary>
        /// <returns>The hardware uptime.</returns>
        public static TimeSpan GetMachineTime()
        {
            return Microsoft.SPOT.Hardware.Utility.GetMachineTime();
        }
    }
}
